﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using Red.Controls.Diagrams.ViewModels;
using Red.Controls.ViewModels;

namespace Red.Controls.Diagrams.Actions
{
    class NodeData
    {
        public string NodeID;
        public double NodeX;
        public double NodeY;
    };

    public class ActionNodeData
    {
        public NodeViewModel NodeViewModel;
        public string ClassName;
        public string NodeID;
        public string ParentID;
        public Point Position;
        public List<ActionConnectionData> Connections;

        public ActionNodeData( NodeViewModel node = null )
        {
            if ( node != null )
                CreateData( node );
        }

        public ActionNodeData( ActionNodeData nodeData )
        {
            NodeViewModel = nodeData.NodeViewModel;
            ClassName = nodeData.ClassName;
            NodeID = nodeData.NodeID;
            ParentID = nodeData.ParentID;
            Position = new Point( nodeData.Position.X, nodeData.Position.Y );
            Connections = nodeData.Connections.Select( connection => new ActionConnectionData( connection ) ).ToList();
        }

        private void CreateData( NodeViewModel node )
        {
            NodeViewModel = node;
            ClassName = node.Class;
            NodeID = node.ID;
            Position = new Point( node.X, node.Y );
            ParentID = string.Empty;
            Connections = NodeViewModel.AttachedConnections.Select( connection => new ActionConnectionData( connection ) ).ToList();
        }
    }

    public class ActionConnectionData
    {
        public ConnectionViewModel Connection;

        public string InputID;
        public string InputOwnerID;
        public string OutputID;
        public string OutputOwnerID;

        public int InputIndex;
        public int InputOwnerIndex;
        public int OutputIndex;
        public int OutputOwnerIndex;

        public string ValueID;

        public ActionConnectionData( ConnectionViewModel connection = null )
        {
            if ( connection != null )
                CreateData( connection );
        }

        public ActionConnectionData( ActionConnectionData connectionData )
        {
            InputID = connectionData.InputID;
            InputOwnerID = connectionData.InputOwnerID;
            OutputID = connectionData.OutputID;
            OutputOwnerID = connectionData.OutputOwnerID;
            InputIndex = connectionData.InputIndex;
            InputOwnerIndex = connectionData.InputOwnerIndex;
            OutputIndex = connectionData.OutputIndex;
            OutputOwnerIndex = connectionData.OutputOwnerIndex;

            Connection = connectionData.Connection;
            ValueID = connectionData.ValueID;
        }


        public void CreateData( ConnectionViewModel connection )
        {
            var inputConnector = connection.DestConnector;
            var outputConnector = connection.SourceConnector;

            if ( connection.DestConnector.Type == ConnectorType.Output )
            {
                inputConnector = connection.SourceConnector;
                outputConnector = connection.DestConnector;
            }

            Connection = connection;
            InputID = inputConnector.ID;
            OutputID = outputConnector.ID;
            InputOwnerID = inputConnector.ParentNode.ID;
            OutputOwnerID = outputConnector.ParentNode.ID;
            ValueID = outputConnector.Property.Values.FirstOrDefault( item => item.Value == inputConnector.ID ).Key;
        }

        public bool FindIndexes( IEnumerable<ActionNodeData> nodesData )
        {
            InputOwnerIndex = FindOwnerIndex( InputOwnerID, nodesData );
            if ( !( InputOwnerIndex < 0 ) )
            {
                var inputOwnerNode = Connection.DestConnector.ParentNode;
                InputIndex = FindConnectorIndex( InputID, inputOwnerNode.InputConnectors );

                OutputOwnerIndex = FindOwnerIndex( OutputOwnerID, nodesData );
                if ( !( OutputOwnerIndex < 0 ) )
                {
                    var outputOwnerNode = Connection.SourceConnector.ParentNode;
                    OutputIndex = FindConnectorIndex( OutputID, outputOwnerNode.OutputConnectors );
                }
                else
                    return false;
            }
            else
                return false;

            return true;
        }

        private int FindConnectorIndex( string connectorID, IEnumerable<ConnectorViewModel> connectors )
        {
            var index = 0;

            foreach ( var connector in connectors )
            {
                if ( connector.ID == connectorID )
                    return index;
                index++;
            }

            return -1;
        }

        private int FindOwnerIndex( string ownerID, IEnumerable<ActionNodeData> nodesData )
        {
            var index = 0;

            foreach ( var nodeData in nodesData )
            {
                if ( nodeData.NodeID == ownerID )
                    return index;
                index++;
            }

            return -1;
        }

    }
    public class InteropActionBase
    {
        #region Data

        protected DiagramViewModel _viewModel;
        protected List<ActionNodeData> _nodesData;
        protected ActionNodeData _nodeData;
        protected DataItem _parentObject;
        protected Func<string, Task> _nodeCreator;

        #endregion Data

        #region Protected methods

        protected async Task<string> CreateNodeAsync( ActionNodeData nodeData, DataItem parentObject, string operationID = "" )
        {
            if ( string.IsNullOrEmpty( operationID ) )
                operationID = Guid.NewGuid().ToString();

            var position = $"{nodeData.Position.X};{nodeData.Position.Y}";
            var nodeID = await parentObject.ExecuteAsync<string>( "createNode", nodeData.NodeID, nodeData.ClassName, position, operationID );
            await _nodeCreator( nodeID.Value );

            return nodeID.Value;
        }

        protected async Task CreateNodesAsync( IEnumerable<ActionNodeData> nodesData, DataItem parentObject )
        { 
            var operationID = Guid.NewGuid().ToString();
            foreach ( var currentNodeData in nodesData )
            {
                // Object validation
                if ( string.IsNullOrEmpty( currentNodeData.NodeID ) )
                {
                    // recreated node needs to have an ID
                    // errorMessage = "Recreated node needs to have an ID!";
                }

                await CreateNodeAsync( currentNodeData, parentObject, operationID );
            }
        }

        protected void RemoveNode( string nodeID, DataItem parentObject, string operationID =  "" )
        {
            if ( string.IsNullOrEmpty( operationID ) )
                operationID = Guid.NewGuid().ToString();

            parentObject.Execute( "removeNode", nodeID, operationID );
        }

        protected bool RemoveNodes( IEnumerable<ActionNodeData> nodesData, DataItem parentObject )
        {
            var operationID = Guid.NewGuid().ToString();
            foreach ( var currentNodeData in nodesData )
            {
                if ( !RemoveAttachedConnections( currentNodeData, operationID ) )
                    return false;

                RemoveNode( currentNodeData.NodeID, parentObject, operationID );                
            }

            return true;
        }

        protected void CreateConnection( ActionConnectionData connectionData, string operationID = "" )
        {
            var connector = GetOutputConnector( connectionData.OutputOwnerID, connectionData.OutputID );
            var propertyName = connector.Property.Name;
            connector.ParentNode.Object.WriteProperty( propertyName, connectionData.InputID, connectionData.ValueID, operationID );
        }

        protected bool RemoveAttachedConnections( ActionNodeData nodeData, string operationID = "" )
        {
            UpdateConnectionData( nodeData );

            foreach ( var connectionData in nodeData.Connections )
                RemoveConnectionData( connectionData, operationID );

            return true;
        }

        protected bool RemoveConnectionData( ActionConnectionData connectionData, string operationID = "" )
        {
            // outputConnector -> inputConnector
            var outputConnector = GetOutputConnector( connectionData.OutputOwnerID, connectionData.OutputID );
            var propertyName = outputConnector.Property.Name;
            var valueID = connectionData.ValueID;

            outputConnector.ParentNode.Object.WriteProperty( propertyName, string.Empty, valueID, operationID );

            return true;
        }

        protected ConnectorViewModel GetInputConnector( string ownerID, string inputID )
        {
            var inputOwner = _viewModel.Network.Nodes.FirstOrDefault( node => node.ID == ownerID );
            return inputOwner.InputConnectors.FirstOrDefault( input => input.ID == inputID );
        }

        protected ConnectorViewModel GetInputConnector( ActionConnectionData connectionData )
        {
            var inputOwner = _viewModel.Network.Nodes.FirstOrDefault( node => node.ID == connectionData.InputOwnerID );
            return inputOwner.InputConnectors.FirstOrDefault( input => input.ID == connectionData.InputID );
        }

        protected ConnectorViewModel GetOutputConnector( string ownerID, string outputID )
        {
            var outputOwner = _viewModel.Network.Nodes.FirstOrDefault( node => node.ID == ownerID );
            return outputOwner.OutputConnectors.FirstOrDefault( output => output.ID == outputID );
        }

        protected ConnectorViewModel GetOutputConnector( ActionConnectionData connectionData )
        {
            var outputOwner = _viewModel.Network.Nodes.FirstOrDefault( node => node.ID == connectionData.OutputOwnerID );
            return outputOwner.OutputConnectors.FirstOrDefault( output => output.ID == connectionData.OutputID );
        }

        protected void UpdateConnectionData( ActionNodeData nodeDataStruct )
        {
            nodeDataStruct.Connections = nodeDataStruct.NodeViewModel.AttachedConnections.Select( connection => new ActionConnectionData( connection ) ).ToList();
        }

        #endregion Protected methods
    }
}
